# ============================================================================
# Facebook Prophet 时间序列预测模型实现
# ============================================================================
# 功能说明：
#   - 使用 Facebook Prophet 进行流量时间序列预测
#   - 支持自动检测趋势、周期性、节假日效应
#   - 适用于接口调用量、用户访问量等业务场景
# 
# 依赖库：
#   - prophet: Facebook开源的时间序列预测库
#   - pandas: 数据处理
#   - numpy: 数值计算
# ============================================================================

from prophet import Prophet
import pandas as pd
import numpy as np
from datetime import datetime, timedelta
import logging

# 创建日志记录器，用于输出模型运行信息
logger = logging.getLogger(__name__)


class ProphetModel:
    """
    Facebook Prophet 时间序列预测模型封装类
    
    Prophet 是 Facebook 开源的时间序列预测工具，特别适合以下场景：
    1. 具有明显周期性的数据（日、周、年）
    2. 存在趋势变化点（如业务增长、活动影响）
    3. 有节假日效应
    4. 有缺失值或异常值
    
    特点：
    - 自动处理周期性（季节性）
    - 自动检测趋势变化
    - 支持节假日建模
    - 提供不确定性估计（置信区间）
    """

    def __init__(self):
        """
        初始化 Prophet 模型
        
        Attributes:
            model: Prophet 模型实例，初始为 None，在 predict 时创建
        """
        self.model = None

    def predict(self, df, forecast_hours=24, confidence_level=0.95):
        """
        使用 Prophet 进行时间序列预测
        
        该方法执行以下步骤：
        1. 数据格式转换（适配 Prophet 要求）
        2. 配置模型参数（周期性、趋势灵活性等）
        3. 训练模型
        4. 执行预测
        5. 返回格式化结果
        
        Args:
            df (pandas.DataFrame): 输入的历史数据，必须包含以下列：
                - 'timestamp': 时间戳（字符串或 datetime 格式）
                - 'value': 观测值（整数或浮点数，如请求次数）
                
            forecast_hours (int, optional): 预测未来多少小时。
                默认值: 24（预测未来一天）
                建议范围: 1-168（1小时到7天）
                
            confidence_level (float, optional): 置信区间水平。
                默认值: 0.95（95%置信区间）
                说明: 0.95 表示有 95% 的概率真实值在置信区间内
                可选值: 0.80, 0.90, 0.95, 0.99
        
        Returns:
            list: 预测结果列表，每个元素是一个字典，包含：
                - timestamp (str): 预测时间点 'YYYY-MM-DD HH:MM:SS'
                - predicted_value (int): 预测值（点估计）
                - confidence_lower (int): 置信区间下界
                - confidence_upper (int): 置信区间上界
                
            示例返回值：
            [
                {
                    'timestamp': '2024-11-03 00:00:00',
                    'predicted_value': 120,
                    'confidence_lower': 95,
                    'confidence_upper': 145
                },
                ...
            ]
        
        Raises:
            ValueError: 如果输入数据格式不正确或数据量不足
            RuntimeError: 如果模型训练或预测失败
        
        注意事项：
            1. 最少需要 48 小时（2天）的历史数据
            2. 建议至少 168 小时（7天）的数据以获得更好的预测效果
            3. 数据应该是连续的小时级数据（可以有少量缺失）
        """
        try:
            # ================================================================
            # 步骤 1: 数据格式转换
            # ================================================================
            # Prophet 要求数据格式：
            # - 'ds': 时间列（datetime 类型）
            # - 'y': 观测值列（数值类型）
            prophet_df = pd.DataFrame({
                'ds': pd.to_datetime(df['timestamp']),  # 将时间戳转换为 datetime
                'y': df['value']  # 将流量值作为观测值
            })

            # ================================================================
            # 步骤 2: 初始化并配置 Prophet 模型
            # ================================================================
            self.model = Prophet(
                # ----------------------------------------------------------
                # 周期性（Seasonality）配置
                # ----------------------------------------------------------
                daily_seasonality=True,
                # 启用日周期性：捕捉24小时内的周期性模式
                # 例如：早高峰（9-11点）、晚高峰（19-21点）
                # 适用场景：大多数业务流量都有日周期特征
                
                weekly_seasonality=True,
                # 启用周周期性：捕捉一周内的周期性模式
                # 例如：工作日流量高、周末流量低
                # 适用场景：ToB业务、办公类应用
                
                yearly_seasonality=False,
                # 关闭年周期性：对于短期预测（几天到几周）不需要
                # 年周期需要至少2年的历史数据才有意义
                # 适用场景：长期战略预测、年度规划
                
                # ----------------------------------------------------------
                # 季节性模式（Seasonality Mode）
                # ----------------------------------------------------------
                seasonality_mode='multiplicative',
                # 乘法模式：周期性变化的幅度与趋势水平成比例
                # 
                # 乘法模式 vs 加法模式：
                # - 乘法（multiplicative）：流量翻倍时，波动也翻倍
                #   适用于：流量、销售额、用户数等（大多数业务指标）
                #   例如：平均100 QPS，波动±20；平均200 QPS，波动±40
                # 
                # - 加法（additive）：波动幅度固定
                #   适用于：温度、库存等物理量
                #   例如：无论流量多少，波动都是±20
                
                # ----------------------------------------------------------
                # 置信区间（Uncertainty Interval）
                # ----------------------------------------------------------
                interval_width=confidence_level,
                # 置信区间宽度：决定预测的不确定性范围
                # 0.95 表示：有 95% 的概率真实值落在 [lower, upper] 区间
                # 
                # 用途：
                # - 资源规划：使用 upper（上界）确保资源充足
                # - 异常检测：实际值超出区间可能表示异常
                # - 风险评估：区间宽度表示预测的可靠程度
                
                # ----------------------------------------------------------
                # 趋势灵活性（Changepoint Prior Scale）
                # ----------------------------------------------------------
                changepoint_prior_scale=0.05,
                # 趋势变化点的先验尺度：控制模型对趋势变化的敏感度
                # 
                # 参数说明：
                # - 默认值：0.05
                # - 范围：0.001 ~ 0.5
                # - 值越大：模型对趋势变化越敏感，拟合越灵活
                # - 值越小：趋势越平滑，避免过拟合
                # 
                # 选择建议：
                # - 0.001-0.01：非常平滑，适用于稳定业务
                # - 0.05：默认值，适用于大多数场景（推荐）
                # - 0.1-0.5：灵活，适用于快速变化的业务
                # 
                # 实际应用：
                # - 新产品上线：0.1-0.2（快速增长）
                # - 成熟产品：0.05（稳定增长）
                # - 衰退产品：0.05-0.1（可能有下降趋势）
                
                # ----------------------------------------------------------
                # 季节性强度（Seasonality Prior Scale）
                # ----------------------------------------------------------
                seasonality_prior_scale=10.0
                # 季节性组件的先验尺度：控制周期性的强度
                # 
                # 参数说明：
                # - 默认值：10.0
                # - 范围：0.01 ~ 100
                # - 值越大：周期性模式越明显
                # - 值越小：周期性被抑制（趋势占主导）
                # 
                # 选择建议：
                # - 1-5：周期性较弱（如企业内部系统）
                # - 10：默认值，适用于大多数场景（推荐）
                # - 15-100：周期性非常强（如电商、社交媒体）
                # 
                # 实际应用：
                # - 2C业务：10-20（周期性明显）
                # - 2B业务：5-10（工作时间特征明显）
                # - 后台任务：1-5（周期性较弱）
            )

            # ================================================================
            # 步骤 3: 添加中国节假日效应
            # ================================================================
            # 节假日对流量的影响（可选配置）
            # 
            # 说明：
            # - Prophet 内置多国节假日数据
            # - 'CN' 代表中国，包含：春节、国庆、中秋、劳动节等
            # - 模型会自动学习节假日前后的流量变化模式
            # 
            # 效果：
            # - 春节前：流量可能激增（购物、抢票）
            # - 春节中：流量可能骤降（休假）
            # - 节后：流量逐渐恢复
            # 
            # 注意：如果预测时间范围不包含节假日，此配置影响不大
            self.model.add_country_holidays(country_name='CN')

            # ================================================================
            # 步骤 4: 训练模型
            # ================================================================
            logger.info("开始训练Prophet模型...")
            logger.info(f"训练数据量: {len(prophet_df)} 个数据点")
            logger.info(f"时间范围: {prophet_df['ds'].min()} 至 {prophet_df['ds'].max()}")
            
            # 使用历史数据拟合模型
            # Prophet 内部会：
            # 1. 分解趋势（Trend）
            # 2. 提取周期性（Seasonality）
            # 3. 识别节假日效应（Holidays）
            # 4. 估计模型参数
            self.model.fit(prophet_df)

            # ================================================================
            # 步骤 5: 创建未来时间点
            # ================================================================
            # 生成未来需要预测的时间序列
            future = self.model.make_future_dataframe(
                periods=forecast_hours,  # 预测未来多少个时间点
                freq='H'  # 时间频率：'H' = 小时（Hourly）
                         # 其他选项：'D'=天，'W'=周，'M'=月
            )
            
            logger.info(f"预测未来 {forecast_hours} 小时")

            # ================================================================
            # 步骤 6: 执行预测
            # ================================================================
            # 对未来时间点进行预测
            # forecast 是一个 DataFrame，包含以下关键列：
            # - ds: 时间点
            # - yhat: 预测值（点估计）
            # - yhat_lower: 置信区间下界
            # - yhat_upper: 置信区间上界
            # - trend: 趋势分量
            # - weekly: 周周期分量
            # - daily: 日周期分量
            # - holidays: 节假日效应
            forecast = self.model.predict(future)

            # ================================================================
            # 步骤 7: 提取预测结果
            # ================================================================
            # 只取未来的预测值（排除历史拟合值）
            # tail(forecast_hours) 获取最后 N 行（即未来的预测）
            predictions = forecast.tail(forecast_hours)[
                ['ds', 'yhat', 'yhat_lower', 'yhat_upper']
            ]

            # ================================================================
            # 步骤 8: 格式化输出
            # ================================================================
            # 转换为 API 返回格式
            results = []
            for _, row in predictions.iterrows():
                results.append({
                    # 时间戳格式化为标准字符串
                    'timestamp': row['ds'].strftime('%Y-%m-%d %H:%M:%S'),
                    
                    # 预测值（点估计）：最可能的值
                    # max(0, ...) 确保预测值不为负（流量不能为负数）
                    'predicted_value': max(0, int(row['yhat'])),
                    
                    # 置信区间下界：悲观估计
                    # 用途：最小资源规划、成本下限
                    'confidence_lower': max(0, int(row['yhat_lower'])),
                    
                    # 置信区间上界：乐观估计
                    # 用途：资源扩容规划、确保服务质量
                    'confidence_upper': max(0, int(row['yhat_upper']))
                })

            logger.info(f"Prophet预测完成，生成 {len(results)} 个预测点")
            logger.info(f"预测值范围: {min(r['predicted_value'] for r in results)} ~ "
                       f"{max(r['predicted_value'] for r in results)}")
            
            return results

        except Exception as e:
            # 捕获并记录所有异常
            logger.error(f"Prophet预测失败: {str(e)}")
            logger.error(f"输入数据量: {len(df)} 行")
            logger.error(f"输入数据列: {df.columns.tolist()}")
            raise

    def get_components(self):
        """
        获取时间序列分解组件（趋势、周期等）
        
        Prophet 将时间序列分解为多个组件：
        1. Trend（趋势）：长期增长或下降趋势
        2. Weekly（周周期）：一周内的周期性变化
        3. Daily（日周期）：一天内的周期性变化
        4. Holidays（节假日）：节假日效应
        
        这些组件可以用于：
        - 可视化分析：理解流量变化的原因
        - 模型诊断：检查模型是否正确捕捉了数据特征
        - 业务洞察：发现业务规律和异常
        
        Returns:
            dict or None: 如果模型已训练，返回可用组件字典；否则返回 None
            
        示例返回值：
            {
                'trend': 'available',      # 趋势组件可用
                'weekly': 'available',     # 周周期可用
                'daily': 'available'       # 日周期可用
            }
        
        使用场景：
            1. 可视化展示：绘制各组件图表
            2. 异常分析：某个组件异常可能表示特定问题
            3. 模型调优：根据组件情况调整参数
        """
        if self.model is None:
            logger.warning("模型尚未训练，无法获取组件信息")
            return None

        # 返回可用的时间序列分解组件
        # 注意：实际应用中可以扩展为返回完整的组件数据
        return {
            'trend': 'available',      # 趋势：业务增长/下降
            'weekly': 'available',     # 周周期：工作日vs周末
            'daily': 'available'       # 日周期：高峰vs低谷
        }